#!/usr/bin/env python
# coding=utf-8


import re
import os
import sys
import struct
import binascii
from socket import *
import glob
import time
#import scapy.all as scapy
import subprocess

policy_host = '10.10.90.201' # policy 服务器
policy_port = 60000  # policy 端口
operator = 'dianxin_dpi' # [yidong, dianxin, dianxin_dpi] # 运营商，对应不同head格式


ip2long = lambda x:sum([256**j*int(i) for j,i in enumerate(x.split('.')[::-1])])
def ipv62long(ipv6_addr): return int(binascii.hexlify(inet_pton(AF_INET6, ipv6_addr)), 16)
time2utc = lambda x:long(time.mktime(time.strptime(x, '%Y-%m-%d %H:%M:%S')))

def get_seq_no(txt='seq_no.txt'):
    """读取第一行的数字为 seq_no 并返回， 写入seq_no+1"""
    with open(txt, 'r+') as f:
        seq_no = int(f.readline().strip())
        f.seek(0)
        f.write('%d\n' % (seq_no + 1))
    return seq_no

def convert_file(txt='msg701.txt', policy_host='192.168.2.183', policy_port=60000, operator='dianxin'):
    ucfile_dir = 'uc_files'
    if not os.path.exists(ucfile_dir):
        os.mkdir(ucfile_dir)
    if not os.path.exists(txt):
        print '[error]: %s does not exist.' % (txt)
        return

    int_format = {'int1': 'B', 'int2': 'H', 'int4': 'I', 'int8': 'Q'}
    if operator in ['dianxin', 'dianxin_dpi']:
        uc_head_field_type = {'ver': 'None', 'resv1': 'int1', 'signature': 'str3', 'type': 'int1', 'no': 'int4',
                          'resv2': 'int1', 'seq': 'int4', 'length': 'int4'} # k,v均小写
    elif operator == 'yidong':
        uc_head_field_type = {'ver': 'int1', 'msgtype': 'int1', 'msgid': 'int4', 'timestamp': 'datetime', 'msgversion': 'int4',
                          'msglength': 'int4'}
    else:
        print '[error]: wrong operator, 实际值：%s，期望值：[yidong, dianxin, dianxin_dpi]' % operator
        return
    msglength_field_index = len(uc_head_field_type) - (1 if operator == 'yidong' else 2) # dianxin的ver字段为None
    lines_format, lines_value = '>', []

    with open(txt) as t:
        print 'open txt file: %s' % txt
        lines = t.readlines()
        line_no = 1
        lines[0] = lines[0].replace('\xef\xbb\xbf','') # 去掉文件开头可能存在的utf-8 BOM
        for line in lines:
            line = re.sub(r'\r?\n?$', '', line)  # 去掉末尾换行符
            #print line
            line_strip = re.sub(r'(?<!\\)#.*$', r'', line)  # 去除#后面的注释, \#后面的不去除
            line_strip = re.sub(r'\\#', r'#', line_strip)  # 替换\#为#
            #line_strip = re.sub(r'(0x[0-9a-fA-F]{2})', eval(r'\1'), line_strip)  # 替换 ascii
            line_strip = re.sub(r'\\r', r'\r', line_strip)  # 替换\r
            line_strip = re.sub(r'\\n', r'\n', line_strip)  # 替换\n
            line_strip = re.sub(r'^\s+$', r'', line_strip)  # 去掉空行中的空白字符; 非空行、非注释中的空白不去除
            #print line, '=>', line_strip 
            if line_strip != '' and (',' in line_strip or ':' in line_strip):
                if ',' in line_strip:
                    field_type, field_value = line_strip.split(',', 1)  # 以第1个逗号做分割
                elif ':' in line_strip:
                    field_type, field_value = line_strip.split(':', 1)
                else:
                    pass
                field_type = field_type.lower()
                if field_type in uc_head_field_type:  # 将消息头字段转换为数字或字符类型
                    field_type = uc_head_field_type[field_type]
                else:
                    pass
                # print field_type, field_value
                if field_type in int_format:  # 数字类型
                    lines_format += int_format[field_type]
                    lines_value += [eval(field_value) if field_value.strip() != '' else 0] # eval转换十进制、十六进制字符串为int
                elif 'str' == field_type:  # 不定长字符类型
                    lines_format += '%ds' % len(field_value)
                    lines_value += [field_value]
                    if len(field_value) not in [lines_value[-2], lines_value[-3], 8, 10]:
                        print '[error]: len(%s)=%d, not in %s, line [%d]:%s' % (lines_value[-1], len(lines_value[-1]), [lines_value[-2], lines_value[-3], 8, 10], line_no, line)
                elif field_type.startswith('str'):  # 定长字符类型
                    lines_format += '%ds' % int(field_type.split('str')[1])
                    lines_value += [field_value + '\00' * (int(field_type.split('str')[1]) - len(field_value))] # 定长时末尾补0
                elif field_type in ['datetime', 'date', 'time'] :  # 时间类型
                    lines_format += 'I'
                    lines_value += [time2utc(field_value)]
                elif 'ipv4' == field_type:  # ipv4类型
                    lines_format += 'I'
                    lines_value += [ip2long(field_value)]
                    if 4 != lines_value[-2]:
                        print '[error]: len(%s)=%d, not %d, line [%d]:%s' % (field_value, 4, lines_value[-2], line_no, line)
                elif 'ipv6' == field_type:  # ipv6类型
                    lines_format += 'QQ'
                    lines_value += [ipv62long(field_value)>>64, ipv62long(field_value)&(2**64-1)]
                    if 16 != lines_value[-3]:
                        print '[error]: len(%s)=%s, not %s, line [%d]:%s' % (field_value, 16, lines_value[-3], line_no, line)
                elif 'None' == field_type:  # 空类型
                    pass
                else:
                    print '[error]: wrong field type in line %d:%s' % (line_no, field_type)
                # print lines_format, lines_value
            elif line_strip != '':
                print '[error]: wrong format, need ":" or ",",  line %d: %s' % (line_no, line)
            else:
                pass  # 空行
            line_no += 1

    # msg post process start
    msg_length = struct.calcsize(lines_format)
    lines_value[msglength_field_index] = msg_length  # 修改消息长度字段
    if operator in ['dianxin', 'dianxin_dpi']:
        lines_value[0] = 0x10  # 修改ver, resv1字段的值为0x10
        #lines_value[msglength_field_index - 1] = get_seq_no() # 修改seq
    # msg post process end

    print 'format: %s\nvalue: %s' % (lines_format, lines_value)
    uc_msg = struct.Struct(lines_format).pack(*lines_value)  # 打包报文
    uc_file = os.path.join(ucfile_dir, re.sub(r'.txt$', '.uc', os.path.basename(txt)))  # 获取uc文件路径
    with open(uc_file, 'wb') as uc_f:
        uc_f.write(uc_msg)  # 写uc文件
    print 'msg (%d bytes): %s\nwrite msg to uc file: %s' % (msg_length, binascii.hexlify(uc_msg), uc_file)
    send_file(uc_file, policy_host, policy_port) # 发送uc文件


def bind(policy_host, policy_port, policy_type, policy_list, action, operator='yidong'):
    yidong_bind_txt="""\
ver:1
msgtype:10
msgid:1
timestamp:2016-7-29 08:52:14#
msgversion:1
msglength:

int1,1#severname no: 0:liukong, 1:buliang
int1,%d#action
int1,0#user type: 0:all, 1:static, 2:group
int1,0#username length
#str,wuchang#username
int1,%d#
""" % (action, len(policy_list))

    dianxin_bind_txt="""\
ver:0#
resv1:1#
signature:CUC#
type:133#
no:956#
resv2:0#
seq:956#
length:74#

int1,%d#Bind_Action:1=绑定, 2=解绑
int1,0#=0x00针对所有机房House_ID；House_Type:2=特定机房
int1,0#House_Type =0x00则本字段取值为0;House_Type=0x02,则本字段为机房ID长度
#str,10027#House_ID，House_Type=0时为空
int2,%d#MessageNum：机房待绑定的策略

""" % (action, len(policy_list))

    dianxin_dpi_bind_txt="""\
ver:0#
resv1:1#
signature:CUC#
type:0x85#
no:57301#
resv2:0#
seq:57301#
length:74#
#==========

int1,%d# action: 0x01-add, 0x02:delete

int1,0x00# user type:0x00-所有用户, 0x01-账号用户,0x02-静态IP地址用户, 0x03-组用户
int1,0# user name length
#str,user2# user name

int2,%d#2# policy num
#int1,0x06# message type
#int4,56301# message no
#int1,0x07# message type
#int4,56401# message no
""" % (action, len(policy_list))


    txt_file = os.path.join(os.getcwd(), 'bind.txt')
    if operator=='yidong':
        bind_txt = yidong_bind_txt
    elif operator=='dianxin':
        bind_txt = dianxin_bind_txt
    elif operator=='dianxin_dpi':
        bind_txt = dianxin_dpi_bind_txt
    else:
        print '[error]: wrong operator, 实际值：%s，期望值: [yidong, dianxin, dianxin_dpi]'
    for policy in policy_list:
        bind_txt += 'int1,%s\n' % policy_type
        bind_txt += 'int4,%s\n' % policy
    with open(txt_file, 'w') as f:
        f.write(bind_txt) 
    convert_file(txt_file, policy_host, policy_port, operator)
        


def send_file(file, policy_host='192.168.2.183', policy_port=60000):
    with open(file, 'rb') as f:
        msg = f.read(-1)
        print 'sending %s(%d bytes) to %s:%s.' % (file, len(msg), policy_host, policy_port)
        #sip_msg = scapy.Ether(src='0c:c4:7a:e4:5f:e8', dst='0c:c4:7a:e4:60:4e') / scapy.IP(src='192.168.2.189',dst=policy_host) / scapy.TCP(dport=policy_port) / msg  # 自定义源IP地址
        #print sip_msg.show()
        #scapy.send(sip_msg)
        conn = socket(AF_INET, SOCK_STREAM) if ':' not in policy_host else socket(AF_INET6, SOCK_STREAM)
        conn.connect((policy_host, policy_port))
        conn.send(msg)
        time.sleep(0.1) # 太快policy程序可能处理不过来
        print 'sent ok!'
        
def get_txts():
    cmd = "openssl enc -des3 -d -in /home/dpi/tools/txts  -out txts.tar.gz -pass pass:yzl123"
    output = subprocess.check_output(cmd + "; exit 0", stderr=subprocess.STDOUT, shell=True)

if __name__ == '__main__':
    print 'args=%s' % sys.argv
    if sys.argv[1] == 'bind':
        bind(policy_host, policy_port, sys.argv[2], sys.argv[3:], 1, operator)
    elif sys.argv[1] == 'unbind':
        bind(policy_host, policy_port, sys.argv[2], sys.argv[3:], 2, operator)
    elif sys.argv[1] == 'txts':
        get_txts()
    else:
        arg_files = sys.argv[1:]
        file_num = 0
        for files in arg_files:    
            for file in glob.iglob(files):
                convert_file(file, policy_host, policy_port, operator)
                file_num += 1
        if file_num == 0:
            print '[error]:',
        print 'converted %d file(s).' % file_num

